package com.minelx.statementsplus.cha.base;

import com.alibaba.fastjson.JSONException;
import com.minelx.landisk.filesystem.core.File;

import javax.sql.DataSource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

import static com.minelx.landisk.filesystem.core.init.OpenPolicy.whatever;
import static com.minelx.statementsplus.cha.base.HttpChaSession.swapBVAL;
import static com.minelx.statementsplus.cha.base.HttpResponseFetcher.INIT_BVAL;
import static java.nio.charset.StandardCharsets.UTF_8;

public class TunnelResponseFetcher extends ResponseFetcher {

	private final DataSource dataSource;

	private final TaskCommitment commitment;

	private int bval;

	public TunnelResponseFetcher(DataSource dataSource, TaskCommitment commitment) {
		this.dataSource = dataSource;
		this.commitment = commitment;
		bval = INIT_BVAL;
	}

	public TunnelDoWorkResponse fetch(int bval) {
		String query = preprocessedMajorSql(commitment.majorSql(), bval);

		// do query
		ByteArrayOutputStream contentStream = new ByteArrayOutputStream();
		int lineCounts = queryAndSave(dataSource, query, contentStream);

		String content = toString(contentStream);
		if (lineCounts == 0) {
			return new TunnelDoWorkResponse(content, 0, true);
		}

		// FIXME 2021/11/25 needed test cover.      multiple situation
		String titleAsText = firstRowContent(content);
		List<String> title = Arrays.asList(titleAsText.split(","));
		int loopKeyIndex = title.indexOf(commitment.loopId());

		String lastRowContent = lastRowContent(content);
		int newBval = Integer.parseInt(lastRowContent.split(",")[loopKeyIndex]);
		return new TunnelDoWorkResponse(content, newBval, false);
	}

	private String firstRowContent(String content) {
		return content.substring(0, content.indexOf('\n'));
	}

	private String lastRowContent(String content) {
		return content.substring(content.lastIndexOf('\n') + 1);
	}

	public static int queryAndSave(DataSource dataSource, String query, OutputStream outputStream) {
		System.out.println("query sql: " + query);
		try (Connection connection = dataSource.getConnection();
			 Statement statement = connection.createStatement();
			 ResultSet resultSet = statement.executeQuery(query)) {
			return new ResultSetWrapper(resultSet).writeThrough(outputStream);
		} catch (SQLException e) {
			throw new RuntimeException("error while obtaining connection.", e);
		}
	}

	public static String preprocessedMajorSql(String majorSql, long bval) {
		String headerRemoved = headerRemoved(majorSql);
		return swapBVAL(headerRemoved, bval);
	}

	static String headerRemoved(String majorSql) {
		return majorSql.replace("2=>", "");
	}

	private static String toString(ByteArrayOutputStream contentStream) {
		try {
			return contentStream.toString(StandardCharsets.UTF_8.name());
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("never gonna happens.", e);
		}
	}

	void forEach(Consumer<String> responseContentConsumer) throws IOException, InterruptedException {
		TunnelDoWorkResponse doWorkResponse = fetch(bval);
		while (!doWorkResponse.isEnd()) {
			// save result content
			String savedContent = preprocessContent(doWorkResponse.content());
			responseContentConsumer.accept(savedContent);

			if (bval != INIT_BVAL) {
				// have a lil rest
				//noinspection BusyWait
				Thread.sleep(500);
			}

			// save next bval
			bval = doWorkResponse.bval();

			// do next query
			doWorkResponse = fetch(bval);
		}
	}

	private String preprocessContent(String responseContent) {
		if (bval == INIT_BVAL) { // init
			// save bval
			return responseContent;
		}
		// save bval
		return responseContent.substring(responseContent.indexOf("\n") + "\n".length());
	}

	@Override
	void extractToFile(File downloadedFile) {
		try (OutputStream output = downloadedFile.outputStream(StandardOpenOption.APPEND)) {
			forEach(content -> {
				try {
					output.write(content.getBytes(UTF_8));
				} catch (IOException e) {
					throw new RuntimeException("error while writing response content to output stream.", e);
				}
			});
			output.flush();
		} catch (IOException | InterruptedException e) {
			throw new RuntimeException("error while doing...", e);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		// delete downloadedFile if no content written.
		if (bval == INIT_BVAL) {
			downloadedFile.path().existence(whatever()).removeIfExists();
		}
	}
}
